// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: hyuan

#include "coin2/exchange/bybit/order_v1/parser.h"

namespace coin2::exchange::bybit::order_v1 {

bool BybitParser::IsBybitSpotErrMessage(const JsonValue& doc) {
  if (doc.HasMember("ret_code")) {
    return (std::string(doc["ret_code"].GetString()) != "0");
  }

  return false;
}

int64_t BybitParser::GetTimestamp(const std::string& time_str) { return stoll(time_str) * 1000000; }

int64_t BybitParser::GetProcOrderId(const JsonValue& data) {
  if (data.HasMember("orderLinkId") && data["orderLinkId"].IsString()) {
    if (!data["orderLinkId"].IsNull()) {
      return stoll(data["orderLinkId"].GetString());
    }
  }
  return 0;
}

int64_t BybitParser::GetWsProcOrderId(const JsonValue& data) {
  if (data.HasMember("c") && data["c"].IsString()) {
    if (!data["c"].IsNull()) {
      return stoll(data["c"].GetString());
    }
  }
  return 0;
}

coin::proto::OrderSide BybitParser::GetOrderSide(const std::string& side) {
  if (side == "BUY") {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (side == "SELL") {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

coin::proto::OrderType BybitParser::GetOrderType(const std::string& type) {
  if (type == "LIMIT") {
    return coin::proto::OrderType::LIMIT_ORDER;
  } else if (type == "MARKET") {
    return coin::proto::OrderType::MARKET_ORDER;
  } else {
    return coin::proto::OrderType::UNKNOWN_ORDER_TYPE;
  }
}

void BybitParser::SetOrderStatus(ProductOrderElement* order, const std::string& status) {
  if ((status == "NEW") || (status == "PENDING_NEW") || (status == "PARTIALLY_FILLED")) {
    order->set_is_live(true);
  } else if (status == "CANCELED") {
    order->set_is_live(false);
    order->set_cancel_confirmed(true);
  } else if (status == "FILLED") {
    order->set_is_live(false);
    order->set_fully_filled(true);
  } else if (status == "PENDING_CANCEL" || status == "REJECTED") {
    order->set_is_live(false);
  } else {
    LOG(ERROR) << "[SetOrderStatus] Unknown order status: " << status;
  }
}

bool BybitParser::IsBalanceUpdate(const rapidjson::GenericValue<rapidjson::UTF8<>>& json) {
  if (json.IsArray() && (json.GetArray().Size() > 0) && json.GetArray()[0].HasMember("e")) {
    return (json.GetArray()[0]["e"] == "outboundAccountInfo");
  }
  return false;
}

bool BybitParser::IsOrderUpdate(const rapidjson::GenericValue<rapidjson::UTF8<>>& json) {
  if (json.IsArray() && (json.GetArray().Size() > 0) && json.GetArray()[0].HasMember("e")) {
    return (json.GetArray()[0]["e"] == "executionReport");
  }
  return false;
}

bool BybitParser::IsTradeUpdate(const rapidjson::GenericValue<rapidjson::UTF8<>>& json) {
  if (json.IsArray() && (json.GetArray().Size() > 0) && json.GetArray()[0].HasMember("e")) {
    return (json.GetArray()[0]["e"] == "ticketInfo");
  }
  return false;
}

void BybitParser::ParseAccountBalance() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseAccountBalance] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_balance = ParseBalance(doc);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountBalance BybitParser::ParseBalance(const JsonValue& doc) {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }
  AccountBalance acc_balance;
  acc_balance.set_exchange("Bybit");
  acc_balance.set_market_type("Spot");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  is_account_balance_initialized_ = true;

  return acc_balance;
}

bool BybitParser::ParseRawBalance(const JsonValue& doc, RawBalanceMap* raw_balance_map) {
  /*
  {
    "ret_code": 0,
    "ret_msg": "",
    "ext_code": null,
    "ext_info": null,
    "result": {
      "balances": [
        {
          "coin": "USDT",
          "coinId": "USDT",
          "coinName": "USDT",
          "total": "10",
          "free": "10",
          "locked": "0"
        }
      ]
    }
  }
  */
  if (IsBybitSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseBalance] Error message\n" << JsonToString(doc);
    return false;
  }

  if (!doc.HasMember("result") || !doc["result"].HasMember("balances") ||
      !doc["result"]["balances"].IsArray()) {
    LOG(ERROR) << "[ParseBalance] Invalid data format\n" << JsonToString(doc);
    return false;
  }

  for (const auto& data : doc["result"]["balances"].GetArray()) {
    if (!data.HasMember("coin") || !data.HasMember("total") || !data.HasMember("free")) {
      continue;
    }
    CurrencyBalance currency_balance;
    currency_balance.set_currency_native(data["coin"].GetString());
    auto total = stod(data["total"].GetString());
    auto avail = stod(data["free"].GetString());
    currency_balance.set_available(avail);
    currency_balance.set_total(total);
    currency_balance.set_hold(total - avail);
    raw_balance_map->emplace(currency_balance.currency_native(), currency_balance);
  }

  return true;
}

void BybitParser::ParseOpenOrderInfo() {
  /*
  {
    "ret_code": 0,
    "ret_msg": "",
    "ext_code": null,
    "ext_info": null,
    "result": [
    {
      "accountId": "10054",
      "exchangeId": "301",
      "symbol": "ETHUSDT",
      "symbolName": "ETHUSDT",
      "orderLinkId": "162080709527252",
      "orderId": "889788838461927936",
      "price": "20000",
      "origQty": "10",
      "executedQty": "0",
      "cummulativeQuoteQty": "0",
      "avgPrice": "0",
      "status": "NEW",
      "timeInForce": "GTC",
      "type": "LIMIT",
      "side": "BUY",
      "stopPrice": "0.0",
      "icebergQty": "0.0",
      "time": "1620807095287",
      "updateTime": "1620807095307",
      "isWorking": true
    }]
  }
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseOpenOrderInfo] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsBybitSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseOpenOrderInfo] Error message\n" << JsonToString(doc);
    return;
  }

  if (!doc.HasMember("result") || !doc["result"].IsArray()) {
    LOG(ERROR) << "[ParseOpenOrderInfo] Invalid data format\n" << JsonToString(doc);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : doc["result"].GetArray()) {
    if (!data.HasMember("symbol")) {
      continue;
    }
    std::string native_symbol = data["symbol"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto order = product_order_bundle->add_each_order();
    ParseOrderInfo(order, data);
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void BybitParser::ParseOrderInfo(ProductOrderElement* order, const JsonValue& data) {
  order->set_exchange_type(ExchangeType::Bybit);
  order->set_market_type(MarketType::Spot);
  order->set_api_version("v1");
  order->set_exchange_order_id(data["orderId"].GetString());
  order->set_symbol(data["symbol"].GetString());
  order->set_price(stod(data["price"].GetString()));
  order->set_qty(std::fabs(stod(data["origQty"].GetString())));
  order->set_proc_order_id(GetProcOrderId(data));
  order->set_side(GetOrderSide(data["side"].GetString()));
  order->set_order_type(GetOrderType(data["type"].GetString()));
  order->set_timestamp(GetCurrentTimestamp());
  SetOrderStatus(order, data["status"].GetString());
}

void BybitParser::ParseHistoryOrderInfo() {
  /*
  {
    "ret_code": 0,
    "ret_msg": "",
    "ext_code": null,
    "ext_info": null,
    "result": [
    {
      "accountId": "10054",
      "exchangeId": "301",
      "symbol": "ETHUSDT",
      "symbolName": "ETHUSDT",
      "orderLinkId": "1620615771764",
      "orderId": "888183901021893120",
      "price": "5000",
      "origQty": "1",
      "executedQty": "0",
      "cummulativeQuoteQty": "0",
      "avgPrice": "0",
      "status": "CANCELED",
      "timeInForce": "GTC",
      "type": "LIMIT",
      "side": "BUY",
      "stopPrice": "0.0",
      "icebergQty": "0.0",
      "time": "1620615771836",
      "updateTime": "1620617056334",
      "isWorking": true
    }]
  }
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseHistoryOrderInfo] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsBybitSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseHistoryOrderInfo] Error message\n" << JsonToString(doc);
    return;
  }

  if (!doc.HasMember("result") || !doc["result"].IsArray()) {
    LOG(ERROR) << "[ParseHistoryOrderInfo] Invalid data format\n" << JsonToString(doc);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : doc["result"].GetArray()) {
    if (!data.HasMember("symbol")) {
      continue;
    }
    std::string native_symbol = data["symbol"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto order = product_order_bundle->add_each_order();
    ParseOrderInfo(order, data);
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void BybitParser::ParseFillOrderInfo() {
  /*
  {
    "ret_code": 0,
    "ret_msg": "",
    "ext_code": null,
    "ext_info": null,
    "result": [
    {
      "id": "931975237315196160",
      "symbol": "BTCUSDT",
      "symbolName": "BTCUSDT",
      "orderId": "931975236946097408",
      "ticketId": "1057753175328833537",
      "matchOrderId": "931975113180558592",
      "price": "20000.00001",
      "qty": "0.01",
      "commission": "0.02000000001",
      "commissionAsset": "USDT",
      "time": "1625836105890",
      "isBuyer": false,
      "isMaker": false,
      "fee": {
        "feeTokenId": "USDT",
        "feeTokenName": "USDT",
        "fee": "0.02000000001"
      },
      "feeTokenId": "USDT",
      "feeAmount": "0.02000000001",
      "makerRebate": "0"
    }]
  }
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseFillOrderInfo] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsBybitSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseFillOrderInfo] Error message\n" << JsonToString(doc);
    return;
  }

  if (!doc.HasMember("result") || !doc["result"].IsArray()) {
    LOG(ERROR) << "[ParseFillOrderInfo] Invalid data format\n" << JsonToString(doc);
    return;
  }

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : doc["result"].GetArray()) {
    auto fill = product_fill_bundle->add_each_fill();
    fill->Clear();
    ParseFillInfo(fill, data);
    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void BybitParser::ParseFillInfo(ProductFillElement* fill, const JsonValue& data) {
  fill->set_symbol(data["symbol"].GetString());
  fill->set_exchange_order_id(data["orderId"].GetString());
  fill->set_price(stod(data["price"].GetString()));
  fill->set_qty(std::fabs(stod(data["qty"].GetString())));
  fill->set_fill_id(data["ticketId"].GetString());
  if (data.HasMember("fee") && data["fee"].HasMember("feeTokenId") && data.HasMember("feeAmount")) {
    fill->set_fee(stod(data["feeAmount"].GetString()));
    fill->set_fee_currency(data["fee"]["feeTokenId"].GetString());
  }
  fill->set_fill_type(
      data["isMaker"].GetBool() ? coin::proto::FillType::MAKER_FILL_TYPE
                                : coin::proto::FillType::TAKER_FILL_TYPE);
  fill->set_fill_timestamp(GetCurrentTimestamp());
}

void BybitParser::ParseSubmitResponse() {
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_timestamp(GetCurrentTimestamp());

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseSubmitResponse] AsioHttpContextStatus error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseSubmitResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (json.HasMember("ret_code") && json.HasMember("ret_msg")) {
    auto error_code = GetOrderErrorCode(json["ret_code"], json["ret_msg"]);
    resp->set_error_code(error_code);
    resp->set_exchange_error_code(GetExchangeOrderErrorCode(json));
    resp->set_exchange_error_msg(GetExchangeOrderErrorMsg(json));
    if (error_code != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
      LOG(ERROR) << "[ParseSubmitResponse] Error response\n" << http_context->res;
      resp->set_success(false);
      return;
    }
  }

  resp->set_success(true);
  if (json.HasMember("result") && json["result"].HasMember("orderId")) {
    resp->set_exchange_order_id(json["result"]["orderId"].GetString());
  } else {
    LOG(ERROR) << "[ParseSubmitResponse] Invalid data format\n" << JsonToString(json);
  }
}

void BybitParser::ParseCancelResponse() {
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(GetCurrentTimestamp());

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseCancelResponse] AsioHttpContextStatus error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseCancelResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (json.HasMember("ret_code") && json.HasMember("ret_msg")) {
    auto error_code = GetOrderErrorCode(json["ret_code"], json["ret_msg"]);
    resp->set_error_code(error_code);
    resp->set_exchange_error_code(GetExchangeOrderErrorCode(json));
    resp->set_exchange_error_msg(GetExchangeOrderErrorMsg(json));
    if (error_code != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
      LOG(ERROR) << "[ParseCancelResponse] Error response\n" << http_context->res;
      resp->set_success(false);
      return;
    }
  }

  resp->set_success(true);
  resp->set_cancel_confirmed(true);
  if (json.HasMember("result") && json["result"].HasMember("orderId")) {
    resp->set_exchange_order_id(json["result"]["orderId"].GetString());
  } else {
    LOG(ERROR) << "[ParseCancelResponse] Invalid data format\n" << JsonToString(json);
  }
}

int32_t BybitParser::ParseErrorMessage(const std::shared_ptr<AsioHttpContext>& http_context) {
  enable_time_.success = false;

  const auto http_code = http_context->res.result_int();
  if (http_code == 403) {
    LOG(ERROR) << "[ParseErrorMessage] Access denied\n" << msg_->DebugString();
  } else if (http_code == 404) {
    LOG(ERROR) << "[ParseErrorMessage] Request path not found\n" << msg_->DebugString();
  } else if (http_code == 429) {
    enable_time_.enable_time = 5 * 1'000'000'000LL + GetCurrentTimestamp();
    enable_time_.success = true;
    LOG(ERROR) << "[ParseErrorMessage] Rate Limit Exceeded\n" << msg_->DebugString();
  }

  return http_code;
}

void BybitParser::ParseWsBalanceInfoList(const JsonDocument& document) {
  /*
  [
    {
      "e":"outboundAccountInfo",
      "E":"1629969654753",
      "T":True,
      "W":True,
      "D":True,
      "B":[
        {
          "a":"BTC",
          "f":"10000000097.1982823144",
          "l":"0"
        }
      ]
    }
  ]
  */
  if (!is_account_balance_initialized_) {
    return;
  }
  auto acc_balance = result_.mutable_account_balance();
  acc_balance->Clear();
  acc_balance->set_exchange("Bybit");
  acc_balance->set_market_type("Spot");
  bool acc_balance_found = false;
  for (const auto& data : document.GetArray()) {
    if (!data.HasMember("B")) {
      continue;
    }
    for (const auto& b : data["B"].GetArray()) {
      if (!b.HasMember("a") || !b.HasMember("f") || !b.HasMember("l")) {
        continue;
      }
      if (!symcache_->has_currency_native(b["a"].GetString())) {
        continue;
      }
      auto* currency = symcache_->GetCurrencyByNative(b["a"].GetString());
      auto* currency_balance = acc_balance->add_each_balance();
      currency_balance->set_currency(currency->currency());
      currency_balance->set_currency_native(currency->native_currency());
      auto avail = stod(b["f"].GetString());
      auto hold = stod(b["l"].GetString());
      currency_balance->set_total(avail + hold);
      currency_balance->set_available(avail);
      currency_balance->set_hold(hold);
      acc_balance_found = true;
    }
  }

  if (acc_balance_found) {
    result_.set_type(MsgType::ACCOUNT_BALANCE);
  }
}

void BybitParser::ParseWsOrderInfoList(const JsonDocument& document) {
  /*
  [
    {
      "e": "executionReport",
      "E": "1499405658658",
      "s": "ETHBTC",
      "c": "1000087761",
      "S": "BUY",
      "o": "LIMIT",
      "f": "GTC",
      "q": "1.00000000",
      "p": "0.10264410",
      "X": "NEW",
      "i": "4293153",
      "M": "0",
      "l": "0.00000000",
      "z": "0.00000000",
      "L": "0.00000000",
      "n": "0",
      "N": "BTC",
      "u": true,
      "w": true,
      "m": false,
      "O": "1499405658657",
      "Z": "473.199",
      "A": "0",
      "C": false,
      "v": "0"
    }
  ]
  */
  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : document.GetArray()) {
    auto order = product_order_bundle->add_each_order();
    order->set_exchange_type(ExchangeType::Bybit);
    order->set_market_type(MarketType::Spot);
    order->set_api_version("v1");
    order->set_exchange_order_id(data["i"].GetString());
    order->set_symbol(data["s"].GetString());
    order->set_price(stod(data["p"].GetString()));
    order->set_qty(std::fabs(stod(data["q"].GetString())));
    order->set_proc_order_id(GetWsProcOrderId(data));
    order->set_side(GetOrderSide(data["S"].GetString()));
    order->set_order_type(GetOrderType(data["o"].GetString()));
    order->set_timestamp(GetCurrentTimestamp());
    SetOrderStatus(order, data["X"].GetString());
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void BybitParser::ParseWsTradeInfoList(const JsonDocument& document) {
  /*
  [
    {
      "e":"ticketInfo",
      "E":"1621912542359",
      "s":"BTCUSDT",
      "q":"0.001639",
      "t":"1621912542314",
      "p":"61000.0",
      "T":"899062000267837441",
      "o":"899048013515737344",
      "c":"1621910874883",
      "O":"899062000118679808",
      "a":"10043",
      "A":"10024",
      "m":true
    }
  ]
  */
  ProductFillBundle* product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : document.GetArray()) {
    auto fill = product_fill_bundle->add_each_fill();
    fill->set_symbol(data["s"].GetString());
    fill->set_fill_timestamp(GetCurrentTimestamp());
    fill->set_exchange_order_id(data["o"].GetString());
    fill->set_price(stod(data["p"].GetString()));
    fill->set_qty(std::fabs(stod(data["q"].GetString())));
    fill->set_fill_id(data["T"].GetString());
    fill->set_fill_type(
        data["m"].GetBool() ? coin::proto::FillType::MAKER_FILL_TYPE
                            : coin::proto::FillType::TAKER_FILL_TYPE);
    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void BybitParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  DLOG(INFO) << "OG ws msg: " << JsonToString(document);
  if (IsOrderUpdate(document)) {
    ParseWsOrderInfoList(document);
  } else if (IsTradeUpdate(document)) {
    ParseWsTradeInfoList(document);
  } else if (IsBalanceUpdate(document)) {
    ParseWsBalanceInfoList(document);
  }
}

auto BybitParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg_->type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  CHECK(msg.type == OrderSubTopicId::REST_MESSAGE);

  int32_t error_code = ParseErrorMessage(msg.http_context);
  if (error_code == 429) {
    if (enable_time_.success) {
      mb_->PublishEvent("bybit/enable_time", reinterpret_cast<void*>(enable_time_.enable_time));
    }
  }

  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
      ParseAccountBalance();
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
      ParseOpenOrderInfo();
      break;
    case OrderSubTopicId::REST_DONE_ORDERS:
      ParseHistoryOrderInfo();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseFillOrderInfo();
      break;
    default: {
      LOG(ERROR) << "[ParseRawMessage] Unknown query type\n" << msg_->DebugString();
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      break;
    }
  }

  return result_;
}

}  // namespace coin2::exchange::bybit::order_v1
